package mwt.xml.xdbforms.schemalayer.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import mwt.xml.xdbforms.dblayer.ConnectionFactory;
import mwt.xml.xdbforms.dblayer.metadata.CatalogMetaData;
import mwt.xml.xdbforms.dblayer.metadata.impl.ColumnMetaData;
import mwt.xml.xdbforms.dblayer.query.Projections;
import mwt.xml.xdbforms.dblayer.query.Query;
import mwt.xml.xdbforms.dblayer.query.SQLOperation;
import mwt.xml.xdbforms.dblayer.query.exception.QueryException;
import mwt.xml.xdbforms.dblayer.query.impl.ProjectionsImpl;
import mwt.xml.xdbforms.dblayer.query.impl.SQLQuery;
import mwt.xml.xdbforms.schemalayer.SchemaDocument;
import mwt.xml.xdbforms.schemalayer.SchemaTypeMapping;
import mwt.xml.xdbforms.schemalayer.exception.SchemaBuildingException;
import mwt.xml.xdbforms.schemalayer.exception.SchemaQueryException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Implementa l'interfaccia SchemaDocument 
 * per la definizione dei metodi di interrogazione e 
 * costruzione del documento xschema
 *
 * 
 * @author Gianfranco Murador, Matteo Ferri, Cristian Castiglia
 */
public class SchemaDocumentImpl implements SchemaDocument {

    private Document schemaDocument = null;
    private SchemaNamespaceResolver nr;
    private SchemaTypeMapping typeMapping;
    private CatalogMetaData catalogMetaData;
    private Element schemaRoot;
    private XPathFactory xpf;
    private List<String> xtypeList;
    private List<String> pkList;
    private boolean insertFKValues;
    private String table = null;
    /**
     * Frammento del documento relativo
     * alla sequenza delle colonne più
     * tipi
     */
    private DocumentFragment colSeqTypeFrag;
    private Element tableSeq;
    /**
     * Frammento relativo alla definizione dell'attributo column
     */
    private DocumentFragment colAttrFragment;
    private Element colAttrRest;

    public SchemaDocumentImpl(Document document) {
        this.schemaDocument = document;

    }

    /**
     *
     *
     * @param nr Risolutore dei namespace per facilitare query xpath
     *
     */
    public SchemaDocumentImpl(SchemaNamespaceResolver nr) {


        this.nr = nr;


        xpf = XPathFactory.newInstance();


        DocumentBuilderFactory dbf =
                DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(nr.hasBinding());
        dbf.setValidating(false);


        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            schemaDocument = db.newDocument();
            schemaRoot = schemaDocument.createElementNS(nr.getNamespaceURI("xs"), "xs:schema");
            schemaRoot.setAttribute("xmlns:xs", nr.getNamespaceURI("xs"));
            schemaDocument.appendChild(schemaRoot);

        } catch (ParserConfigurationException ex) {
            Logger.getLogger(SchemaDocumentImpl.class.getName()).log(Level.SEVERE, null, ex);
        }



    }

    /**
     * Costruisce il documento,
     * se il documenoto viene già fornito nel costruttore
     * allora questo metodo non è richiamabile
     *
     *
     * @param table Nome della tabella da cui estrarre i metadati
     *
     * Nel codice chiamante usare una un statement finally per
     * verificare la chiusura della connessione fatta dal
     * CatalogMetaData
     *
     * ES:
     *<code>
     *  try{
     *
     *     CatalogMetaData catalogMetaData = factory.getDataBaseMetaData();
     *
     *     ... Create Builder of Document
     *
     *     schemadoc.build("table");
     *
     *  }catch(SchemaBuildingException ex){
     *
     *     ex.printStackTrace();
     *
     *  } finally{
     *
     *    if(catalogMetaData != null){
     *       catalogMetaData.close();
     *    }
     *
     *  }
     *</code>
     *
     *
     */
    public void build(String table, String database, boolean insert) throws SchemaBuildingException {

        ColumnMetaData columnMetaData;
        ResultSet rsMetaData = null;
        ResultSet rsFK = null; // ResultSet per prendere le foreing key
        ResultSet rsPK = null;
        xtypeList = new ArrayList<String>();
        pkList = new ArrayList<String>();

        ConnectionFactory cf = ConnectionFactory.getInstance();
        ConnectionFactory.setDatabase(database);

        catalogMetaData = cf.getDatabaseMetaData();



        typeMapping = new SchemaTypeMappingHandler(schemaDocument);


        colSeqTypeFrag = schemaDocument.createDocumentFragment();
        colAttrFragment = schemaDocument.createDocumentFragment();
        this.table = table;
        insertFKValues = insert;

        addTableType(table);
        defineAttributes();

        try {
            // per ogni colonna
            // addBasicTypes

            rsFK = catalogMetaData.getImportedKeys(ConnectionFactory.getDatabase(), null, table);

            rsMetaData = catalogMetaData.getColumns(ConnectionFactory.getDatabase(), null, table, null);

            rsPK = catalogMetaData.getPrimaryKeys(ConnectionFactory.getDatabase(), null, table);


            while (rsPK.next()) {

                pkList.add(rsPK.getString("COLUMN_NAME"));


            }





            while (rsMetaData.next()) {

                columnMetaData = new ColumnMetaData();

                String colName = rsMetaData.getString("COLUMN_NAME");
                String typeName = rsMetaData.getString("TYPE_NAME");
                String displaySize = rsMetaData.getString("COLUMN_SIZE");
                String fractional = rsMetaData.getString("DECIMAL_DIGITS");
                String defaultValues = rsMetaData.getString("COLUMN_DEF");
                String isNullable = rsMetaData.getString("IS_NULLABLE");
                String isAutoIncrement = rsMetaData.getString("IS_AUTOINCREMENT");


                if (typeName.contains("UNSIGNED")) {

                    throw new SchemaBuildingException("Error: the unsigned types are not supported, " +
                            "cause they aren't part of any SQL Standard");

                }

                if (isNullable.equals("YES")) {
                    columnMetaData.setNullable(true);
                } else {
                    columnMetaData.setNullable(false);
                }

                if (isAutoIncrement.equals("YES")) {
                    columnMetaData.setAutoIncrement(true);
                } else {
                    columnMetaData.setAutoIncrement(false);
                }


                columnMetaData.setColName(colName);
                columnMetaData.setTypeName(typeName);

                if (displaySize != null) {
                    columnMetaData.setDisplaySize(Integer.parseInt(displaySize));
                }

                if (fractional != null) {

                    columnMetaData.setFractional(Integer.parseInt(fractional));
                }

                columnMetaData.setDefaultValue(defaultValues);

                if (typeName.equals("ENUM")) {

                    columnMetaData.setEnumSetValues(catalogMetaData.getEnumValues(table, colName));

                }


                if (typeName.equals("SET")) {

                    columnMetaData.setEnumSetValues(catalogMetaData.getSetValues(table, colName));

                }


                DocumentFragment df = typeMapping.getSchemaType(columnMetaData);


                if (!isTypeDefined(columnMetaData)) {
                    addBasicType(df);
                    xtypeList.add(columnMetaData.getXschemaTypeName());
                }

                df = buildFKType(rsFK, columnMetaData);

                if (df != null) {
                    addBasicType(df);
                }




                addColumnType(columnMetaData);
            //addColumnAttrRestriction(columnMetaData.getColName());

            }

        } catch (SQLException ex) {
            Logger.getLogger(SchemaDocumentImpl.class.getName()).log(Level.SEVERE, null, ex);
            throw new SchemaBuildingException("Metadata retriving error!", ex);
        } finally {

            if (rsMetaData != null) {
                try {
                    rsMetaData.close();
                } catch (SQLException ex) {
                    throw new SchemaBuildingException("Fatal Error on close ResultSet ", ex);
                }

            }

            if (rsFK != null) {
                try {
                    rsFK.close();
                } catch (SQLException ex) {
                    throw new SchemaBuildingException("Fatal Error on close ResultSet ", ex);

                }

            }

            if (rsPK != null) {
                try {
                    rsPK.close();
                } catch (SQLException ex) {
                    throw new SchemaBuildingException("Fatal Error on close ResultSet ", ex);

                }

            }


        }




        //  defineValidValues();
        schemaRoot.appendChild(colSeqTypeFrag);
        schemaRoot.appendChild(colAttrFragment);
        Element data = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:element");
        data.setAttribute("name", "data");
        data.setAttribute("type", table);

        schemaRoot.appendChild(data);

        // I metadati del database vengono chiusi qui.

        catalogMetaData.close();
        catalogMetaData = null;
    }

    /**
     * Aggiunge il frammento allo schema xml,
     * riguardante il tipo base di un campo.
     * 
     * @param typeFragment Frammento XML
     */
    private void addBasicType(DocumentFragment typeFragment) {
        schemaRoot.appendChild(typeFragment);
    }

    /**
     * Aggiunge il contenuto delle colonne.
     * <code>
     * &lt;xs:element name="tablename" type="tabletype"/&gt;
     * </code>
     * @param catalogMetaData Metadati della colonna
     */
    private void addColumnType(ColumnMetaData cmd) {


        Element xelement;
        xelement = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI,
                "xs:element");
        xelement.setAttribute("name", cmd.getColName());

        /**
         *
         * <xs:complexType>
         *			<xs:simpleContent>
         *				<xs:extension base="INT">
         * 					<xs:attribute ref="getAutoincrementTypeName" use="optional"/>
         *					<xs:attribute ref="isPrimaryKey" use="optional"/>
         *				</xs:extension>
         *			</xs:simpleContent>
         *		</xs:complexType>
         */
        if (cmd.isAutoIncrement()) {



            Element xcomplexType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:complexType");
            Element xsimpleContent = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:simpleContent");
            Element xextension = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:extension");
            xextension.setAttribute("base", cmd.getXschemaTypeName());
            Element xattrAutiInc = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:attribute");
            xattrAutiInc.setAttribute("ref", "isAutoincrement");
            xattrAutiInc.setAttribute("use", "required");
            xattrAutiInc.setAttribute("fixed", "true");

            /*
            Element xattrPK = schemaDocument.createElement("xs:attribute");
            xattrPK.setAttribute("ref", "isPrimaryKey");
            xattrPK.setAttribute("use", "optional");
            xcomplexType.appendChild(xsimpleContent);
            xextension.appendChild(xattrPK);
             */

            xcomplexType.appendChild(xsimpleContent);

            xsimpleContent.appendChild(xextension);
            xextension.appendChild(xattrAutiInc);
            xelement.appendChild(xcomplexType);
        } else {
            xelement.setAttribute("type", cmd.getXschemaTypeName());
        }

        if (cmd.getDefaultValue() != null) {
            String defaultVal = cmd.getDefaultValue();
            if (defaultVal.equals("CURRENT_TIMESTAMP")) {

                Date today = new Date();
                defaultVal = (new Timestamp(today.getTime())).toString();
                defaultVal = defaultVal.replace(' ', 'T');

                int point = defaultVal.indexOf('.');
                defaultVal = defaultVal.substring(0, point);



            }

            xelement.setAttribute("default", defaultVal);
        }

        if (cmd.isNullable()) {
            xelement.setAttribute("nillable", "true");

        } else {
            xelement.setAttribute("nillable", "false");
        }



        tableSeq.appendChild(xelement);


    }

    /**
     * Aggiunge il tipo complesso
     * @param tableName
     */
    private void addTableType(String tableName) {

        Element tableType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:complexType");
        tableType.setAttribute("name", tableName);
        tableSeq = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:sequence");
        tableType.appendChild(tableSeq);
        colSeqTypeFrag.appendChild(tableType);

    }

    /**
     * Aggiunge la definizione dell'attributo columns
     * 
     */
    private void defineAttributes() {

        Element xattr, xsimpleType;

        /*
        xattr = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:attribute");
        xattr.setAttribute("name", "column");

        xsimpleType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:simpleType");
        xattr.appendChild(xsimpleType);


        colAttrRest = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:restriction");
        colAttrRest.setAttribute("base", "xs:string");
        xsimpleType.appendChild(colAttrRest);
        colAttrFragment.appendChild(xattr);*/


        /**
         * <xs:attribute name="getAutoincrementTypeName">
         *  <xs:simpleType>
         * <xs:restriction base="xs:boolean">
         * </xs:restriction>
         * </xs:simpleType>
         * </xs:attribute>
         * 
         * <xs:attribute name="isPrimaryKey">
         * <xs:simpleType>
         * <xs:restriction base="xs:boolean">
         * </xs:restriction>
         * </xs:simpleType>
         * </xs:attribute>
         */
        xattr = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:attribute");
        xattr.setAttribute("name", "isAutoincrement");
        xsimpleType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:simpleType");
        xattr.appendChild(xsimpleType);
        Element xrest = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:restriction");
        xrest.setAttribute("base", "xs:boolean");
        xsimpleType.appendChild(xrest);
        xattr.appendChild(xsimpleType);
        colAttrFragment.appendChild(xattr);

    /*
    xattr = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI,"xs:attribute");
    xattr.setAttribute("name", "isPrimaryKey");
    xsimpleType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI,"xs:simpleType");
    xattr.appendChild(xsimpleType);
    xrest = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI,"xs:restriction");
    xrest.setAttribute("base", "xs:attribute");
    xsimpleType.appendChild(xrest);
    xattr.appendChild(xsimpleType);
    colAttrFragment.appendChild(xattr);
     */

    }

    /**
     *
     * @param columnName
     *
    private void addColumnAttrRestriction(String columnName) {

    Element xenum = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:enumeration");
    xenum.setAttribute("value", columnName);
    colAttrRest.appendChild(xenum);

    }*/
    /**
     * 
     *
    private void defineValidValues() {
    Element element,
    complexType,
    attrib,
    seq,
    elemval;

    element = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:element");
    element.setAttribute("name", "admissible-values");
    element.setAttribute("minOccurs", "0");
    element.setAttribute("maxOccurs", "unbounded");

    complexType = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:complexType");
    seq = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:sequence");
    elemval = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:element");
    elemval.setAttribute("name", "value");
    elemval.setAttribute("type", "xs:string");
    elemval.setAttribute("minOccurs", "1");
    elemval.setAttribute("maxOccurs", "unbounded");
    seq.appendChild(elemval);
    attrib = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:attribute");
    attrib.setAttribute("ref", "column");
    attrib.setAttribute("use", "required");

    element.appendChild(complexType);
    complexType.appendChild(seq);
    complexType.appendChild(attrib);
    tableSeq.appendChild(element);


    }*/
    /**
     * Cerca se un tipo è già stato definito
     * @param catalogMetaData
     * @return true
     */
    private boolean isTypeDefined(ColumnMetaData cmd) {

        String typeName = cmd.getXschemaTypeName();

        if (xtypeList.contains(typeName)) {
            return true;
        } else {
            return false;
        }


    }

    /**
     *
     * @param fkRs ResultSet delle chiavi esterne importate nella tabella
     * @param catalogMetaData metadati della colonna
     * @return Frammento xschema per le chiavi esterne, null se la colonna non
     * ha un riferimento alla chiave esterna.
     * @throws mwt.xml.xdbforms.schemalayer.exception.SchemaBuildingException
     */
    private DocumentFragment buildFKType(ResultSet fkRs, ColumnMetaData cmd)
            throws SchemaBuildingException {

        String colName = cmd.getColName();
        String fkname, xtypename, pkColname, pkTableName;
        DocumentFragment fkTypeFrag = null;
        Element xtype, restriction;

        try {


            while (fkRs.next()) {

                fkname = fkRs.getString("FKCOLUMN_NAME");


                if (fkname.equals(colName)) {

                    // la colonna ha un riferimento ad una chiave esterna
                    pkColname = fkRs.getString("PKCOLUMN_NAME");
                    pkTableName = fkRs.getString("PKTABLE_NAME");
                    xtypename = cmd.getTypeName() + "_FK_" + pkTableName + "_" + pkColname;

                    fkTypeFrag = schemaDocument.createDocumentFragment();
                    xtype = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:simpleType");
                    xtype.setAttribute("name", xtypename);
                    restriction = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:restriction");
                    restriction.setAttribute("base", cmd.getXschemaTypeName());
                    cmd.setXschemaTypeName(xtypename);

                    Collection fkvalue = catalogMetaData.getForeingKeyValue(pkColname, pkTableName);

                    System.out.println("SIZE COLLECTION ::->"+fkvalue.size());
                    if (insertFKValues) {
                        for (Iterator it = fkvalue.iterator(); it.hasNext();) {

                            List<String> l = (List) it.next();

                            for (int i = 0; i < l.size(); i++) {
                                String val = l.get(i);
                                Element enumeration = schemaDocument.createElementNS(XMLConstants.W3C_XML_SCHEMA_NS_URI, "xs:enumeration");
                                enumeration.setAttribute("value", val);
                                restriction.appendChild(enumeration);
                            }

                        }
                    }

                    xtype.appendChild(restriction);
                    fkTypeFrag.appendChild(xtype);

                    return fkTypeFrag;


                }
            }

            fkRs.first();
            fkRs.previous();




        } catch (SQLException ex) {
            throw new SchemaBuildingException("Building foreign key error ", ex);
        }



        return fkTypeFrag;

    }

    /**
     * Ritorna il tipo della colonna definito
     * all'interno della colonna
     * @param colName nome della colonna
     * @return nome del tipo della colonna
     */
    public String getColumnType(String colName) throws SchemaQueryException {

        // Definire un query xpath
        String xpathQuery = "//xs:element[@name='" + colName + "']";
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nr);


        try {
            XPathExpression expression = xp.compile(xpathQuery);

            Object res = expression.evaluate(schemaDocument, XPathConstants.NODESET);
            NodeList nodeList = (NodeList) res;


            /**
             * Deve esistere un solo nodo con attributo name uguale
             * secondo il modello relazionale.
             */
            if (nodeList.getLength() != 1) {

                throw new SchemaQueryException("SchemaDocumentImpl.getColumnType(): Error, column not exists");
            }

            /**
             * TODO: Rimuovere il ciclo for..
             */
            for (int i = 0; i < nodeList.getLength(); i++) {
                NamedNodeMap nnm = nodeList.item(i).getAttributes();
                if (nnm.getLength() == 0) {
                    throw new SchemaQueryException("SchemaDocumentImpl.getColumnType: Error, attribute retrival error");
                }

                return nnm.getNamedItem("type").getNodeValue();

            }


        } catch (XPathExpressionException ex) {
            throw new SchemaQueryException("SchemaDocumentImpl.getColumnType()", ex);
        }
        return null;
    }

    /**
     * Ritorna il nome del tipo della colonna,
     * come definito nel documento
     * @param id numero cardinale della colonna
     * @return il tipo della colonna in formato string
     * @deprecated 
     */
    public String getColumnType(int id) throws SchemaQueryException {


        String xpathQuery = "//xs:element";
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nr);
        try {
            XPathExpression expression = xp.compile(xpathQuery);
            Object res = expression.evaluate(schemaDocument, XPathConstants.NODESET);
            NodeList nodeList = (NodeList) res;

            if (nodeList.getLength() == 0) {

                throw new SchemaQueryException("Error, no column found");
            }

            Node node = nodeList.item(id);

            if (node == null) {

                throw new SchemaQueryException("Error, id out of range");

            } else {

                NamedNodeMap nnm = nodeList.item(id).getAttributes();
                if (nnm.getLength() == 0) {
                    throw new SchemaQueryException("SchemaDocumentImpl.getColumnType: Error, attribute retrival error");
                }

                return nnm.getNamedItem("type").getNodeValue();

            }

        } catch (XPathExpressionException ex) {
            throw new SchemaQueryException("SchemaDocument.getColumnType(id) ", ex);
        }


    }

    /**
     * Ritorna tutti tipi delle colonne
     * @return 
     */
    public String[] getAllColumnsType() throws SchemaQueryException {

        String xpathQuery = "//xs:complexType//xs:element[not(@maxOccurs)]";
        List<String> list = new ArrayList<String>();
        XPath xp = xpf.newXPath();
        String typeName = null;
        String colname = null;
        String type;
        xp.setNamespaceContext(nr);


        try {

            XPathExpression expression = xp.compile(xpathQuery);
            Object res = expression.evaluate(schemaDocument, XPathConstants.NODESET);
            NodeList nodeList = (NodeList) res;
            for (int i = 0; i < nodeList.getLength(); i++) {
                NamedNodeMap nnm = nodeList.item(i).getAttributes();
                if (nnm.getLength() == 0) {
                    return new String[0];
                }

                colname = nnm.getNamedItem("name").getNodeValue();

                if ((typeName = getAutoincrementTypeName(colname)) == null) {
                    type = nnm.getNamedItem("type").getNodeValue();
                    //  System.out.println(colname+" di tipo "+type);
                    list.add(type);
                } else {

                    //  System.out.println(colname+ " di tipo "+typeName);

                    list.add(typeName);

                }
            }

            return list.toArray(new String[0]);


        } catch (XPathExpressionException ex) {
            throw new SchemaQueryException("Error in expression evaluation", ex);

        }

    }

    /**
     * @param colname
     * @return
     * @throws mwt.xml.xdbforms.schemalayer.exception.SchemaQueryException
     */
    public String getColumnBaseType(String colname) throws SchemaQueryException {

        String xpQueryColumn = "//xs:element[@name='" + colname + "']";
        String typeName = null;
        String xpQueryType = null;


        XPath xp = xpf.newXPath();



        try {
            XPathExpression expression =
                    xp.compile(xpQueryColumn);

            Object res = expression.evaluate(schemaDocument, XPathConstants.NODE);
            Node node = (Node) res;

            NamedNodeMap nnm = node.getAttributes();
            typeName = nnm.getNamedItem("type").getNodeValue();

            xpQueryType = "//xs:simpleType[@name='" + typeName + "']/xs:restriction";

            expression = xp.compile(xpQueryType);

            res = expression.evaluate(schemaDocument, XPathConstants.NODE);
            node = (Node) res;

            nnm = node.getAttributes();
            return nnm.getNamedItem("base").getNodeValue();

        } catch (XPathExpressionException ex) {

            throw new SchemaQueryException("XPath query error", ex);

        }

    }

    public String[] getAllColumnsName() throws SchemaQueryException {

        // String xpathQuery = "//xs:element";

        String xpathQuery = "//xs:complexType//xs:element[not(@maxOccurs)]";
        String name;
        List<String> list = new ArrayList<String>();

        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nr);


        try {
            XPathExpression expression = xp.compile(xpathQuery);
            Object res = expression.evaluate(schemaDocument, XPathConstants.NODESET);
            NodeList nodeList = (NodeList) res;


            for (int i = 0; i < nodeList.getLength(); i++) {

                NamedNodeMap nnm = nodeList.item(i).getAttributes();
                if (nnm.getLength() == 0) {
                    return new String[0];
                }

                name = nnm.getNamedItem("name").getNodeValue();


                list.add(name);

            }

            return list.toArray(new String[0]);

        } catch (XPathExpressionException ex) {
            ex.printStackTrace();
            throw new SchemaQueryException("XPath Query error", ex);
        }

    }

    public Document getSchemaDocument() throws SchemaBuildingException {
        if (schemaDocument != null) {
            return schemaDocument;
        } else {
            throw new SchemaBuildingException("Error, document was not builded yet!");
        }
    }

    public String getDafaultValue(String colName) throws SchemaQueryException {

        String xpathQuery = "//xs:element[@name='" + colName + "']";
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nr);


        try {
            XPathExpression expression = xp.compile(xpathQuery);
            Object res = expression.evaluate(schemaDocument, XPathConstants.NODE);
            Node n = (Node) res;

            NamedNodeMap nnm = n.getAttributes();

            n = nnm.getNamedItem("default");

            if (n == null) {
                return null;
            } else {
                return n.getNodeValue();
            }


        } catch (XPathExpressionException ex) {
            throw new SchemaQueryException(ex);
        }

    }

    public boolean hasFK(String colName) {
        String typeName = "";
        try {
            typeName = getColumnType(colName);
        } catch (SchemaQueryException ex) {
            Logger.getLogger(SchemaDocumentImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (typeName.contains("_FK_")) {
            return true;
        } else {
            return false;
        }
    }

    public String getAutoincrementTypeName(String colName) {

        String xpathQuery = "//xs:element[@name='" + colName + "']/xs:complexType/xs:simpleContent/xs:extension";

        //System.out.println(xpathQuery);
        String typeName = null;
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nr);
        XPathExpression expression;
        try {
            expression = xp.compile(xpathQuery);
            Object res = expression.evaluate(schemaDocument, XPathConstants.NODESET);

            NodeList nl = (NodeList) res;

            if (nl.getLength() == 0) {

                System.out.println(colName + " non e' autoincr");

                return null;
            } else {

                for (int i = 0; i < nl.getLength(); i++) {

                    System.out.println(colName + " e' autoincr");

                    Node n = nl.item(i);

                    System.out.println("NODE NAME  " + n.getNodeName());

                    // if (n.getNodeName().equals("xs:extension")) {

                    NamedNodeMap nnm = n.getAttributes();
                    typeName = nnm.getNamedItem("base").getNodeValue();
                    System.out.println("typeName : " + typeName);
                    break;

                //}


                }

                System.out.println("***()TypeName   "+typeName);
                return typeName;

            }



        } catch (XPathExpressionException ex) {
            Logger.getLogger(SchemaDocumentImpl.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

    }

    public String getTableName() {
        return table;
    }

    /**
     * TODO: Posso usare XPath per fare l'interrogazione.
     */
    public String[] getEnumValues(String colname) {

        ConnectionFactory cf = ConnectionFactory.getInstance();
        catalogMetaData = cf.getDatabaseMetaData();

        String[] val = catalogMetaData.getEnumValues(table, colname);
        catalogMetaData.close();
        catalogMetaData = null;
        return val;


    }

    /***
     * TODO: Usare XPath per fare le query sul documento,
     * visto che ora i valori ammissibili per le chiavi esterne
     * sono mappate nello schema
     * @param colname
     * @param tablename
     * @return
     */
    public Collection getForeingKeyValue(String colname, String tablename) {

        Collection c = null;
        ConnectionFactory cf = ConnectionFactory.getInstance();
        Query q = cf.getQuery();
        SQLOperation sqlo = new SQLQuery(tablename);
        Projections p = new ProjectionsImpl();
        p.property(colname);
        sqlo.addProjections(p);
        sqlo.compile();
        try {
            c = q.read(sqlo);
        } catch (QueryException ex) {
            Logger.getLogger(SchemaDocumentImpl.class.getName()).log(Level.SEVERE, null, ex);
        }

        return c;

    }

    /**
     * TODO: posso usare xpath per l'interrogazione,
     * se uso un pattern per i valori SET.
     * @param colname
     * @return
     */
    public String[] getSetValues(String colname) {

        ConnectionFactory cf = ConnectionFactory.getInstance();
        catalogMetaData = cf.getDatabaseMetaData();

        String[] val = catalogMetaData.getSetValues(table, colname);
        catalogMetaData.close();
        catalogMetaData = null;
        return val;

    }

    public String[] getAllPrimaryKeysNames() {
        return pkList.toArray(new String[0]);
    }
}
